查看原文
其他

号称目前本世代最强画质之一的游戏,如何打造绝美天空奇景?

Angelou Lv 腾讯GWB游戏无界 2023-10-16

引言 在去年的SIGGRAPH 2022上,Guerrilla Games的首席环境艺术家兼技术美术工程师Andrew Schneider讲述了更新后的Nubis系统如何用于解决《地平线2:西之决境》中的天空造景问题,这次更新意在解决体积云渲领域的3个重要课题:分层渲染问题、地形云问题、特殊气象模拟。当然还有一些其他的方向,比如云的演化【一定环境条件下“母云”会转化为不同的“衍生云”,转化云具有新的特征,而母云原本的特征逐渐消失不见】,云的交互等等。但相对热门,或者说更期盼优先得到解决的是上面三个方面。接下来是一篇内容量超多的正文,内容成文于去年下旬。

作者:Angelou Lv
腾讯互动娱乐 技术美术
(转载请征得同意,文章仅为作者观点,不代表GWB立场)

相比较于2015、2017年的工作,2022年在密度模型、云的GI计算、Raymarching机制以及美术工作流上都有所调整。首先是真正的引入了分层渲染的概念



包含了两个子层,分别用于层积云与卷云。同时引入了NDF(Nubis Data Field)用来存储构建云的密度模型所需的必要信息。NDF是2D数据,渲染时从NDF中提取相应信息,通过对应的计算转换为3D体积云所需要的控制项。在我看来,这一操作的目的有二,其一是将云塑型所需要的关键数据进行抽象,统筹来降低塑形的难度,其二是独立的数据封装便于模块化与解决设计整条工作流的环节衔接问题,类似于Bridge的作用。


总之这些数据被映射到16km * 16km的区域内用于渲染时采样以获取构建 3D 云景所需的数据。要注意的是两个对流子层所需要的NDF是不同的,且两个子层的照明模型与Raymarching细节也是不同的。


1.1 NDF结构


对于Stratus Sub-Layer,NDF大概是这样的:



Stratus Sub-Layer被限定在256m~2048m的球壳范围内,其需要NDF包含:


● Cloud Min Height:最小高度

● Cloud Max Height:最大高度

● Cloud Coverage:云的覆盖

● Cloud Bottom Type:云的底部类型

● Cloud Top Type:云的顶部类型

● Influence Mask:用于NDF Generator与NDF Editor所生成字段的混合


让我们先忘记NDF,假设要求用最少的控制去描述积云的外形,能够抽象出哪些参数呢?用要求最苛刻的浓积云举例子,首先需要知道云的顶部与底部被限定在什么范围内,这决定了塔的高度,也就是Min/Max Height;然后,我们知道浓积云另一个典型特征是具有紧实,凭证的底部,于是可能希望有一个专门的参数控制这一块的程度,也就是Cloud Bottom Type;接着,我们还希望能够控制塔顶的形态,比如从底部沿着塔身至顶部的过渡,那么至少需要知道顶部的形态才能做插值,也就是Cloud Top Type;最后,我们肯定需要一个参数控制天空哪些区域是有浓积云的,也就是Cloud Coverage。通过对规律的总结,发现至少需要5个参数【或者6个,假使希望能够进一步对底部-顶部的过渡进行控制的话】才能大致描述出浓积云的外形信息。这就是Sub Layer NDF设计的意义。


有了控制数据,就要讨论如何从这些数据还原云的具体形态与结构信息:



也就是密度模型的构建过程。


1.2 Dimensional Profile模型


新的密度模型被称为Dimensional Profile Model【立体空间剖面模型】,它的一部分设计沿袭了2015年就奠定的思路,同时在其基础上强化。



Dimensional Profile = Vertical Profile * Cloud Coverage


空间剖面模型被拆分为“垂直剖面“与”水平剖面“。水平剖面,很好理解,就是描述云在天空分布覆盖信息的Cloud Map;垂直剖面,则表示沿着海拔方向的切片。它是两个乘数的积,这两个乘数分别是Cloud Top Type与Cloud Bottom Type:



其中Cloud Top Type描述了云顶部的轮廓形状:



V轴表示高度,U轴代表了云属类型。如果我们要描述密度随高度变化的概率,那么层云,层积云与积云的高度范围应该是下面这样的,这样特定高度下Type为混合三个概率的值。



这也是从SIG 2015第一代云开始就使用的老办法(高度梯度函数),下面三张图分别展示了Type值在0.4,0.35,0.1时所形成的云属类型,分别为积云(Cumulus),层积云(Stratocumulus), 层云(Stratus):



到了2022年的方案中,额外追加了一张用于控制底部轮廓的密度剖面:



Cloud Bottom Type从0到1变化:



对比以前的工作,可以认为是用更全面的Dimensional Profile Model代替了早期的Height Gradient Function来确定云属。


1.3 密度模型构建


1.3.1 空间剖面生成大型


Dimensional Profile描述了云的大型轮廓:



1.3.2 采样噪声雕刻细节


细节的雕刻需要采样一张3D噪声,在3D噪声纹理的选择上与之前一致,第一张为128 * 128 * 128的四通道纹理,R通道为Perlin-Worley Noise噪声,GBA为频率递增的低频Worley噪声:



3个低频噪声按梯度权重混合成”分型噪声“,再与Perlin-Worley噪声进行数据重映射得到最终的Composite Noise,参考代码如下:


// 采样低频噪声,硬件3D采样, Talk中的原型方案

float SampleLowFrequencyNoises(float3 p, float mip_level)

{


    float4 low_frequency_noises = tex3Dlod(tCloud3DNoiseTextureA,  sCloud3DNoiseSamplerA, float4 (p, mip_level) ).rgba;


    // 从低频 Worley 噪声中构建一个 fBm,可用于为低频 Perlin-Worley 噪声添加细节

    // 这主要用于改善连贯的Perlin-worley噪声,使其产生孤立的岛状云

    float low_freq_fBm = ( low_frequency_noises.g * 0.625 ) + ( low_frequency_noises.b * 0.25 ) + ( low_frequency_noises.a * 0.125 );


    // 通过使用由 Worley 噪声构成的低频 FBM 对其进行膨胀来定义基本云形状。

    float cloud_noise_composite  = Remap( low_frequency_noises.r, - ( 1.0 -  low_freq_fBm), 1.0, 0.0, 1.0 );


    return cloud_noise_composite ;

}


从Cloud Noise Composite中减去Dimensional Profile的反转值雕刻云,归一化后得到云密度:


float cloud_density = saturate(cloud_noise_composite - (1.0 - dimensional_profile));


如下图:



1.3.3 引入风场


在用于3D纹理与空间剖面采样的坐标基础上减去风的偏移值:



1.4 照明模型优化


2017年的光照模型由三个部分组成:

● 方向散射:两个米氏散射做插值,虽然不物理,但能提高太阳高度较低时远离太阳处云的效果

● 外散射与吸收:借鉴13年Magnus Wrennnge的多重散射近似思路组合两个Beer计算,取其最大值

● 内散射函数:用来替代2015年的“糖粉效果”,由深度概率函数与垂直概率函数组成


到了2022年,云的照明模型被分为“直接散射”与“环境散射”两部分:



1.4.1 Direct Scattering


直接散射的计算公式如下:


// 直接散射被定义为3个概率的函数

// 透过率

// 多次散射

// 散射相位

Direct Scattering =

(Transmittance * Primary Scattering Phase) + (Multiple Scattering *Secondary Scattering Phase)


Transmittance项依旧使用Beer Lambert定律;使用HG方程计算米氏散射的相位函数;这里重点说明下Multiple Scattering项的计算。一个思想实验是:多重散射发生在光线穿越体积时,且散射的程度与体积内的深度有关。深度越大的地方散射结果在统计学上越趋向于各向同性;而对于那些靠近体积轮廓边缘的地方,散射发生的次数相对较少,吸收越弱,越趋向于原本前向散射的包络。这样的趋势就导致了,对于一片较厚的云,当其挡在视线与太阳之间时,边缘更加透亮,因为相较于中心更少的光线被改变了方向;而当我们背对太阳望向一片云时,同样的规律会带来不同的视觉效果,云的边缘相较于中心较厚的位置有概率散射超过180°的比例更低,这导致积云花椰菜外观在边缘处的深色结果。


既然如此,就可以通过Dimensional Profile与步进距离综合判断采样点处在云内部什么位置:



使用ms_volume作为概率场来描述内散射发生的频率,attenuated_light是Beer定律造成的能量衰减,height_fraction用来调整垂直方向的内散射,实际作用更多的是为了减少底部的内散射好让底部更暗,通过两个指数引入手动控制。


1.4.2 Ambient Scattering


来此云彼此的环境散射考虑的简单些,主要考虑来自上方与周围的云贡献了对效果影响更明显的贡献。由于Dimensional Profile已经提供了云外部到内部的梯度,因此:



1.4.3 Cloud GI


Direct Scattering,Ambient Scattering,GI的对比如下:



1.5 Raymarching调整


现在,我们回过头来看下Raymarching部分的改变。


沿着摄像机发射射线,根据采样点的坐标计算密度,如果返回值为0,继续下一步,如果返回值不为>0说明已经进入轮廓大型内部,累积密度并进入光照循环。这里似乎没有使用2015年的“圆锥采样”思路,而是设定10个光照采样点, 并且步幅随着远离初始采样点逐渐增大,这样贡献更多的近处密度信息使用了更精确的步进,而贡献相对较弱的外层密度信息也不会浪费太多步进:



注意,从PPT内容看,View Ray与Light Ray应该都不是恒定步幅的,毕竟跳跃式Raymarching在2015年的方案中就用上了,没必要在新的版本中割舍:



对于View Ray上那些需要计算光照的采样点来说,每次步进计算颜色与Alpha,进行累加



这里的light_absorption是指上一次步进计算出的Alpha,它是当前采样点的密度返回值与上一步light_absorption Inverse的乘积,相乘的结果会做为这一步的Alpha;这一步采样点上所能接收的光强light intensity,是当前的光能,当前的采样密度返回值,当前的Alpha三项的乘积;Light intensity 根据 Ambient 或 Sun 光源着色并输出到颜色通道。


1.6 Cirrus Sub-Layer的处理


第二层云,也就是Cirrus Sub-Layer,在《西部禁域》中卷云层被定义为2048m以上的区域,使用一个独立的更轻量的NDF


1.6.1 Cirrus NDF



包括:

● Cloud Coverage:定义卷云的水平覆盖

● Cloud Type:定义卷云的云属与云种

● Influence Mask:用于NDF Generator与NDF Editor生成字段的混合


1.6.2 Cirrus 模型


计算模型上,Cirrus Sub-Layer使用与Stratus Sub-Layer相同的光照模型,但密度模型更简单,采样次数也有所限制。


用三个平铺纹理实现不同的卷云样式,由ValueRemap完成cr_streaky,cr_wispy,cr_round三种云种的插值过渡:



由于卷云在海拔上非常扁平,体积非常轻薄。考虑到这一点在计算密度时省去了View Ray上的循环步进,而是选择直接由覆盖率的重映射值做为指数去驱动Density的幂函数【这是一个非真实的密度模型】。Light Ray上的步进也由10次降低到4次:



因此Cirrus并没有按照真实的3D体渲染去做,被称为2.5D。 


1.7 设计工作流


2017年提出的“作家系统”,也就是Nubis编辑器与以之为核心的工作流程也得到了进一步整合



数据资产的设计与渲染通过NDFs桥接,与之前Cloudmap的生成类似,也保留了“程序化”与人工编辑两套路线,由Influence NDFs模块进行整合,NDF Generator负责生成主要的云,这部分占80%;剩下20%由美术手动完成,这部分云通常为需要定制的,有特殊表现需要的。



上文有提到,Stratus Sub-Layer与Cirrus Sub-Layer的NDFs中都包含一个Influence Mask用于两套流程下云的混合,这样手动创建的云景便只能影响整个云景的一部分,由此实现独立控制。



下面展示了黄昏时分两层云的流动,但构成地平线附近云景的手工制作的云仍留在原地【这部分云并没有在天空发生位移,它们的风动画是由噪声侵蚀时引入的】




2022年引入了地形云,它们通常贴敷在山脉的迎风坡,特点是形状受地形影响,高度间距更窄,且通常角色能够穿过去。



由于地形云的这些特点,其密度模型,光照模型,Raymarching细节都要额外设计。


2.1 Envelope模型


为地形云准备了一套性能更优秀的密度模型被称为“包络模型”【Envelope Model】:



Envelope Profile类似Vertical Profile,只不过使用高度数据(而不是去采样Profile)来计算轮廓信息以便降低性能消耗,计算过程如上图。


2.2 地形云密度模型构建


2.2.1 包络模型构成大型


使用Envelope Profile计算出来的密度场如下:



2.2.2 复合噪声侵蚀细节


然后依旧使用Noise Composite去侵蚀轮廓剖面雕刻细节的思路。


地形云使用了两种组合噪声:wispy noise与billiowy noise。一种视觉上较为纤细,一种视觉上更加“波涛汹涌”。同样由Cloud Type定义高度梯度,然后对两种噪声进行线性插值:



下面展示了Cloud Type 从1~0过程中地形云的细节变化



侵蚀计算如下:



2.2.3 风场设计细节


为了增强地形云的动态效果,包络模型使用的风场要更复杂些



2.3 地形云光照模型


同样由Direct Scattering与Ambient Scattering两项组成。直接散射如下:



直接散射是Beer-Lambert定律计算的透过率,与long_distance_shadow_sample的乘积。第二项的含义是对于那些会被山体挡住的云来说,需要一个类似阴影的Factor。这个Factor由采样距离场阴影图得到。注意上图“绿色圈”中两部分云的光照差异!


环境散射的处理类似分层渲染中的思路,Cloud Coarse Density是经由复合噪声侵蚀包络模型得到的,本身隐含了体积贡献信息,这里额外乘上高度梯度是为了防止云底部的环境贡献过多:



2.4 地形云的Raymarching机制


包络模型和垂直剖面模型云的光线行进之间有两个区别。首先,在128 米以上而不是 256 米沿着Light Ray去采集10 个样本;其次,是使用了“Cone Step Mapping”【松弛圆锥步进】技术。


如下图:



包络高度数据用于生成定义两个锥体的两个斜率值,我们再定义一个最高相机高度,一个最低相机高度,这样View Ray发生在圆锥内的步进就是有效步进。当光线行进时,可以在光线和这些锥体之间进行相交测试,以确定在击中任何云层之前能够采取的最大步长。如果我们在云层上方行进,就使用上锥体,如果我们在云层下方,则使用底部锥体。当处于最小和最大高度之间时,再采用更小的射线行进步骤并对实际云密度进行采样。



对比使用垂直剖面模型直接Raymarching与使用Envelope模型进行圆锥步进采样,发现能节省大量无效的采样计算消耗!



2.5 地形云的NDFs


包络模型有自己的NDF,使用了7个数据字段:



前4个用于构建包络剖面



分别为:

● Cloud Min Height:最小高度

● Cloud Max Height:最大高度

● Cloud Type:地形云类型(2种,在Shader中做插值过渡)

● Cloud Density:额外的密度项,用于根据地形对密度场进行调控


后3个用作射线加速,分别为:

● Cloud Distance:摄像机到云的距离

● 上圆锥角

● 下圆锥角


2.6 地形云的设计流程


Author System中使用NDFs Editor编辑地形云。在Houdini中对照地形直接刷,然后生成NDFs数据给到Decima Engine。





《西之绝境》中出现了“超级风暴”【SuperCells】的云景模拟 ,它拥有同“分层云”类似的密度模型,以便降低复杂度,融入对流层的其他云。但使用专门设计的一套照明与Raymarching机制。


[ 注意:这是实拍,非渲染!]


3.1 气旋模拟


3.1.1 风暴特征


抽象出了几个特征来描述风暴:

● 中心气旋(Mesocyclone):这是最重要的特征,风暴绕中心旋转,且越靠近中心的地方速度越慢,越远离中心的地方旋转越快

● 沿气旋延伸至顶部的砧型结构,上部较平,沿中气旋柱扩散到上层云中

● 独特的内部照明:风暴一般蕴含着巨大的能量,闪电在之间形成,打量了体积内部


3.1.2 SuperCells NDFs


SuperCells的密度模型与对流层中的其他云类似,但NDFs多了一个mask:



这个Mask用来定位SuperCell在对流层中的位置,同时定义作用半径。


3.2 Superstorm密度建模


3.2.1 气旋柱建模


基于“垂直剖面模型”,将几种噪声混合并用圆形遮罩为中心取最大值形成中气旋柱,Bottom Cloud  Type来控制气旋底部



接下来要解决旋转问题。尝试随时间旋转围绕气旋中心的细节噪声采样位置,如下:



然后想办法做出中心旋转慢,周围旋转快的速度场。为时间偏移乘上一组增量值。这形成了一组同心环样式的速度场:



在这基础上增加一组扭曲,为噪声的旋转添加一些失真,以产生流体边界相互作用的错觉:



这里作者提到了一个潜在隐患,当云的移动速度快于图像构建速度时,TAA会导致漩涡像素糊在一起。解决思路是计算每个环的运动矢量,然后可以在Shader中使用该向量来对像素更新进行排序,以此解决快速移动的涡流中可能出现的时间伪影。参考做法如下:



3.2.2 砧顶建模


在对流层出现超级风暴的区域,翻转Vertical Profile,然后将查找结果与正常的Vertical Profile查找结果结合起来,以便云顶部也产生水平分布,同时倾斜该区域噪声以产生风吹过的外观偏移:



这样就得到了Supercelll的最终Vertical Profile。对于砧顶向上延伸至Cirrus Sub-layer的部分,对NDFs进行一些更改,使用Location,Radius,Wind Vector在风暴中气旋上方形成卷云并沿风向偏移



由于超级风暴使用与对流层云层其余部分相同的建模数据,从云到超级风暴的过渡是无缝的。


3.3 Superstorm照明模型


3.3.1 主光照模型


同样由Dirtect Scattering与Ambient Scatering组成。


首先看Direct Scattering,针对风暴的Raymarching工作示意如下:



以气旋柱中心为轴线,半径Radius为范围构建一个区域【上图中的红框】,通过靠近超级风暴中心的距离dist来描述密度样本规模增加的概率。也就是说,如果光样本落在给定半径范围内,那么就进行密度的累加,累加的密度通过Beer定律计算透光率。


然后是Ambient Scattering,构建了一个圆柱形概率场(类似于用作衰减的概率场,但随着高度的增加概率逐渐降低),目的在于控制风暴中心气旋底部的环境光贡献量:



与其他云的模拟不同,Supercell通常还有用于氛围营造的额外光源。它们来源于风暴内部的能量释放与伴随产生的放电现象。我们称之为“风暴的内部照明”。由此可见,内部照明源有两类,一个是能量释放,一个是随机迸发的闪电。


3.3.2 额外光源:云团内能量的照明计算


这里的做法是直接在风暴中心增加一个光源打亮内部,但没有针对此光源追加额外的Raymarching,而是再次将照明考虑为几何概率问题。即将原本View Ray上各采样点沿Light Ray循环步进累积光能的过程简化为View Ray上围绕光源采集的每个样本中存储的光能。听起来有些绕,下面过程描述的对比图能更加清晰地阐释这一思路:



具体地计算模型是【参考最右边的】:

● 首先,定义一个球形体积,代表能够检测到来自内部发光的区域,设球的半径为radius

● 其次,将势能定义为从样本到光源中心的线性距离的函数,假设采样点Sample pos到光源的距离为d1,那么采样点的潜在能量potential energy为d1的函数;

● ,定义一个高度梯度height gradient,表示光衰减的比率

● 然后,考虑介质内部并不是均质的,所以要拟合一个Pseudo attention。表示全密度样本数据由于局部密度变化引起的光吸收概率

● 接着,潜在能量,垂直梯度,异质密度效应的乘积为该采样点样本中存储的光能

● 最后,将该光强值添加到光能函数的结果中


3.3.3 额外光源:闪电的照明建模


考虑了两种闪电样式:“内部闪光”与“地面放电照明”【即从云层到地面的闪电】



对于内部闪电的照明,使用与内部能量释放相同的方法。只是为每个闪电提供一个可变位置,搭配模拟闪电产生规律的触发模式。地面放电效应由粒子系统实现,使用Decimas Effect Graph工具控制效果。两套闪电的配合机制是:系统首先会执行多个云内闪效果,同时每次都增加闪光强度;然后它会随机触发1~2个地面放电效果。


但是这一特效系统的正常工作同样受分帧的影响,具体的解决办法暂不展开分析。



未来会引入Nubis 体积数据模型,即NVDF【Nubis Volume Data Fields】。



NVDF至少包含两个3D浮点字段:云的体积密度,云的3D距离场数据。有了距离场数据,每次光线能够有效的直接定位云的表面,然后使用距离场作为步幅直接获取体积密度。







您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存